# -*- coding: utf-8 -*-

from functools import wraps
import logging
import traceback

from werkzeug.local import LocalProxy
from itsdangerous import SignatureExpired, BadTimeSignature, BadSignature

current_user = LocalProxy(lambda: _get_user())
current_miniapp = LocalProxy(lambda: _get_miniapp())

from odoo.http import request
from ..meta import Singleton
from ..serializer import DefaultSerializer
from ..redis import RedisClient
from ..exceptions import WechatMiniAppException, WechatAuthException
from ...adaptor.factory import WechatAppFactory

logger = logging.getLogger(__name__)


class Auth(object, metaclass=Singleton):
    def __init__(self):
        self.secret_key = request.env['ir.config_parameter'].sudo().get_param('wechat.secret_key') or None
        self.session_expiration = int(request.env['ir.config_parameter'].sudo().get_param('wechat.session_expiration')) or None

        if not self.secret_key or not self.session_expiration:
            raise WechatMiniAppException(
                errcode=202001,
            )
        self.serializer = DefaultSerializer(
            key=self.secret_key,
            expiration=self.session_expiration,
        )
        self.redis = RedisClient()

    def login(self, code, site):
        wechat_app_factory = WechatAppFactory()
        app = wechat_app_factory[site]

        if app.app_type == 'miniapp':
            session_info = self.get_wx_session_info(app, code)
            if session_info is None:
                raise WechatAuthException(
                    errcode=101003,
                )
            session_info['user_source'] = 'miniapp'
        elif app.app_type == 'mpapp':
            session_info = self.get_oauth_user_info(app, code)
            if session_info is None:
                raise WechatAuthException(
                    errcode=101004,
                )
            session_info['user_source'] = 'mpapp'
            # session_info = {'unionid': None, 'city': '\u5e7f\u5dde', 'language': 'zh_CN', 'avatar': 'http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKWmyUG3MkgzGfEfqIicNn18S6sRQd1Zjlas5XaPGBYf2tbUUHwJRPmI5SxIrQPTyiatQVvtwMfFPng/132', 'nickname': 'SimonQ', 'province': '\u5e7f\u4e1c', 'gender': 1, 'country': '\u4e2d\u56fd', 'user_source': 'mpapp', 'openid': 'o1RCUw3VZYB38bWrWidSU87fjD1M'}
        else:
            raise WechatAuthException(
                errcode=101005,
            )

        session_info['site'] = site
        user = request.env['wechat.user'].login(session_info)
        token = self.serializer.dump_user(user)

        self.redis.login_user(user)
        request.uid = request.env.ref('wechat.host_user').id

        return user, token

    def get_wx_session_info(self, app, code):
        try:
            session_info = app.wxapi.exchange_code_for_session_key(code=code)
            # session_info = {'session_key': 'm13RpY2DBog2i/UBRan7kA==', 'openid': 'oMsoG0SE7WqooVSAiOODolH19s70'}
        except:
            logger.error(
                'Exchange session code FAILED. %s Err: %s',
                code,
                traceback.format_exc(),
            )
            session_info = None
        else:
            logger.info('Retrieved user info %s -> %s', code, session_info)
        return session_info

    def get_oauth_user_info(self, app, code):
        return app.oauth_login(code)

    def verify_session_token(self, token):
        site = None
        try:
            data = self.serializer.loads(token)
        except SignatureExpired:
            logger.info('Token expired for %s', token)
            raise WechatAuthException(
                errcode=102003,
            )
        except (BadSignature, BadTimeSignature):
            logger.info(
                'Token data corrupted: %s',
                token,
            )
            raise WechatAuthException(
                errcode=102004,
            )
        logger.debug('Deserialized user token: %s -> %s', token, data)
        user = self._get_user_from_token(data)
        request.uid = request.env.ref('wechat.host_user').id
        return user

    def _get_user_from_token(self, data):
        user_type = data.get('user_type')
        uid = data.get('uid')
        site = data.get('site')

        if not uid:
            raise WechatAuthException(
                errcode=102005,
            )
        user_info = self.redis.load_user(uid)

        if user_info is not None and isinstance(user_info, dict):
            user = request.env['wechat.user'].get_proxy(**user_info)
            if user is not None:
                logger.debug('Loaded user proxy for: %s', uid)
                return user
            logger.error(
                'Getting wechat user proxy failed, trying loading from db %s',
                uid,
            )

        user = request.env['wechat.user'].sudo().search([('id', '=', uid)])
        if not user:
            raise WechatAuthException(
                errcode=102006,
            )
        user = user[0]
        self.redis.login_user(user)
        return user


def _get_user():
    if hasattr(request, 'wechat_user'):
        return request.wechat_user

    auth_header = request.httprequest.headers.get('authorization')
    auth_session = request.session.auth_token
    if not auth_header and not auth_session:
        raise WechatAuthException(
            errcode=102001,
        )

    if auth_header:
        try:
            _, token = auth_header.split(' ')
        except:
            logger.warning(
                'Token format error %s Error: %s',
                auth_header,
                traceback.format_exc()
            )
            raise WechatAuthException(
                errcode=102002,
            )
        else:
            logger.info('Retrieved user token from headers: %s', token)
    elif auth_session:
        token = auth_session
        # logger.info('Retrieved user token from session: %s', token)

    auth = Auth()
    user = auth.verify_session_token(token)

    request.wechat_user = user

    return user


def _get_miniapp():
    if hasattr(request, 'miniapp'):
        return request.miniapp
    site = current_user.site
    miniapp = WechatAppFactory()[site]

    request.miniapp = miniapp

    return miniapp


def login_required(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        current_miniapp._get_current_object()
        return f(*args, **kwargs)
    return wrapper
